﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Drawing;
using System.Drawing.Imaging;

namespace LowLevelGraphics.SeamCarving
{
    /// <summary>
    /// Seam carving sobel function class
    /// </summary>
    public sealed class Sobel : EnergyFunction
    {
        /// <summary>
        /// ToString
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return "Sobel";
        }

        /// <summary>
        /// Recompute
        /// </summary>
        /// <param name="bmd"></param>
        /// <param name="size"></param>
        public override void Recompute(BitmapData bmd, Size size)
        {
            if (bmd != null)
            {
                EnergyMap = new byte[bmd.Width, bmd.Height];
                for (int x = 0; x < bmd.Width; x++)
                {
                    for (int y = 0; y < bmd.Height; y++)
                    {
                        EnergyMap[x, y] = GetSobelValue(bmd, size, x, y);
                    }
                }
            }
        }

        /// <summary>
        /// Recompute the Sobel energy map based on the seam that changed (to be efficient)
        /// </summary>
        /// <param name="bmd"></param>
        /// <param name="size"></param>
        /// <param name="seam"></param>
        public override void RecomputeSeam(BitmapData bmd, Size size, Seam seam)
        {
            if (bmd != null)
            {
                // Iterate through seam
                foreach (Point p in seam.SeamPoints(size))
                {
                    // Make sure to include the pixels to the "left" too
                    if (seam.Direction == Direction.Vertical)
                    {
                        if (p.X > 0)
                            EnergyMap[p.X - 1, p.Y] = GetSobelValue(bmd, size,
                            p.X - 1, p.Y);
                        if (p.X < size.Width)
                            EnergyMap[p.X, p.Y] = GetSobelValue(bmd, size, p.X
                            , p.Y);
                    }
                    else
                    {
                        if (p.Y > 0)
                            EnergyMap[p.X, p.Y - 1] = GetSobelValue(bmd, size,
                            p.X, p.Y - 1);
                        if (p.Y < size.Height)
                            EnergyMap[p.X, p.Y] = GetSobelValue(bmd, size, p.X
                            , p.Y);
                    }
                }
            }
        }

        /// <summary>
        /// Get a Sobel value for a given pixel
        /// </summary>
        /// <param name="bmd"></param>
        /// <param name="size"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        private static byte GetSobelValue(BitmapData bmd, Size size, int x,
        int y)
        {
            if (x >= 0 && x < size.Width && y >= 0 && y < size.Height)
            {
                int[] dx = { -1, 0, +1, -1, 0, +1, -1, 0, +1 },
                dy = { -1, -1, -1, 0, 0, 0, +1, +1, +1 };
                byte[] p = new byte[9];
                int sobelX, sobelY, sobel;
                for (int i = 0; i < p.Length; i++)
                    p[i] = EnergyFunction.GetEnergyPixel(bmd, size, x + dx[i],
                    y + dy[i]);
                sobelX = (p[0] + (p[1] + p[1]) + p[2] - p[6] - (p[7] + p[7]) -
                p[8]);
                sobelY = (p[2] + (p[5] + p[5]) + p[8] - p[0] - (p[3] + p[3]) -
                p[6]);
                sobel = (int)Math.Sqrt((sobelX * sobelX) + (sobelY * sobelY));
                return (byte)Math.Min(sobel, byte.MaxValue);
            }
            else
            {
                // Out of bounds
                return byte.MaxValue;
            }
        }
    }
}
